package apigateway.project.core.filter.router;

import apigateway.project.commen.config.Rule;
import apigateway.project.commen.enums.ResponseCode;
import apigateway.project.commen.exception.LimitedException;
import apigateway.project.core.CacheConfig;
import apigateway.project.core.aspect.FilterAspect;
import apigateway.project.core.aspect.Order;
import apigateway.project.core.context.GlobalContext;
import apigateway.project.core.filter.Filter;
import apigateway.project.core.helper.AsyncHttpHelper;
import apigateway.project.core.helper.ResponseHelper;
import apigateway.project.core.redis.RedisUtils;
import apigateway.project.core.request.GlobalRequest;
import apigateway.project.core.response.GlobalResponse;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy;
import com.netflix.hystrix.*;
import lombok.extern.slf4j.Slf4j;
import org.asynchttpclient.Request;
import org.asynchttpclient.Response;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException;

import static apigateway.project.commen.constant.FilterConst.*;

@FilterAspect(value = ROUTER_FILTER_ID,name = ROUTER_FILTER_NAME )
@Order(ROUTER_FILTER_ORDER)
@Slf4j
public class RouterFilter implements Filter {

    @Override
    public void doFilter(GlobalContext ctx) throws Exception {
        Rule.CircuitBreakerConfig breakerConfig = ctx.getRule().getBreakerConfig();
        Rule.HystrixConfig hystrixConfig = getHystrixConfig(ctx);
        if (breakerConfig != null) {
            routeWithSentinel(ctx, breakerConfig);
        }else if (hystrixConfig != null) {
            routeWithHystrix(ctx, hystrixConfig);
        }else{
            route(ctx);
        }
    }

    /**
     * 初始化Sentinel熔断规则
     * @param breakerConfig
     */
    private static void initDegradeRule(String resourceName, Rule.CircuitBreakerConfig breakerConfig ){

        if(!DegradeRuleManager.hasConfig(resourceName)){
            DegradeRule degradeRule = new DegradeRule(resourceName);

            degradeRule.setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
                    .setCount(breakerConfig.getCount())
                    .setTimeWindow(breakerConfig.getTimeWindow())
                    .setSlowRatioThreshold(breakerConfig.getSlowRatioThreshold())
                    .setMinRequestAmount(breakerConfig.getMinRequestAmount())
                    .setStatIntervalMs(breakerConfig.getStatIntervalMs());

            synchronized (DegradeRuleManager.class) {
                CacheConfig instance = CacheConfig.getCacheConfig();
                if(!DegradeRuleManager.hasConfig(resourceName)) {
                    instance.putDegradeRule(degradeRule);
                    DegradeRuleManager.loadRules(instance.getDegradeRules(resourceName));
                }
            }
        }
    }

    /**
     * redis分布式熔断
     * @param ctx
     * @param breakerConfig
     */
    private void routeWithRedisCircuitBreaker(GlobalContext ctx,Rule.RedisCircuitBreakerConfig breakerConfig){
        // redis zset key = ip + port
        String key = ctx.getRequest().getModifyHost()+ctx.getRequest().getPath();

        RedisCircuitBreaker redisCircuitBreaker = new RedisCircuitBreaker(new RedisUtils());

        try {
            long startTime = System.currentTimeMillis();
            route(ctx);
            long endTime = System.currentTimeMillis();
            long responseTime = endTime - startTime;
            boolean circuitBreakOpen = true;
            circuitBreakOpen = redisCircuitBreaker.doCircuitBreak(key, breakerConfig, responseTime);
            if(!circuitBreakOpen){
                throw new LimitedException(ResponseCode.GATEWAY_FALLBACK);
            }
        }catch (Exception e){
            if(e instanceof LimitedException){
                ctx.setReponse(GlobalResponse.buildGlobalResponse(ResponseCode.GATEWAY_FALLBACK));
            }
            else{
                ctx.setReponse(GlobalResponse.buildGlobalResponse(breakerConfig.getFallbackResponse()));
            }
        }
    }

    /**
     * 开启Sentinel熔断下路由
     * @param ctx
     * @param breakerConfig
     */
    private void routeWithSentinel(GlobalContext ctx, Rule.CircuitBreakerConfig breakerConfig){
        //对实例具体接口熔断
        String resourceName = ctx.getRequest().getModifyHost()+ctx.getRequest().getPath();

        initDegradeRule(resourceName,breakerConfig);
        Entry entry = null;
        try {
            entry = SphU.entry(resourceName);
            //核心业务
            route(ctx);

        } catch (BlockException e) {
            //服务降级
            if(e instanceof DegradeException){
                ctx.setReponse(GlobalResponse.buildGlobalResponse(ResponseCode.GATEWAY_FALLBACK));
            }
            else {
                ctx.setReponse(GlobalResponse.buildGlobalResponse(breakerConfig.getFallbackResponse()));
            }
        }finally {
            if(entry != null){
                entry.exit();
            }
        }
    }
    /**
     * 开启Hystrix熔断下路由
     * @param ctx
     * @param hystrixConfig
     */
    private void routeWithHystrix(GlobalContext ctx, Rule.HystrixConfig hystrixConfig) {
        HystrixCommand.Setter setter = HystrixCommand.Setter
                .withGroupKey(HystrixCommandGroupKey.Factory.asKey(ctx.getRule().getId()))
                .andCommandKey(HystrixCommandKey.Factory.asKey(ctx.getRequest().getPath()))
                .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(hystrixConfig.getThreadCoreSize()))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        //线程池
                        .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
                        //超时时间
                        .withExecutionTimeoutInMilliseconds(hystrixConfig.getTimeoutInMilliseconds())
                        .withExecutionIsolationThreadInterruptOnTimeout(true)
                        .withExecutionTimeoutEnabled(true)
                );

            new HystrixCommand<Void>(setter) {

            @Override
            protected Void run() throws Exception {
                route(ctx);
                return null;
            }

            @Override
            protected Void getFallback() {
                if(isFailedExecution() && getFailedExecutionException() instanceof TimeoutException){
                    //超时服务降级
                    ctx.setReponse(GlobalResponse.buildGlobalResponse(ResponseCode.GATEWAY_FALLBACK));
                }else{
                    //其它情况的服务降级
                    ctx.setReponse(GlobalResponse.buildGlobalResponse(hystrixConfig.getFallbackResponse()));
                }
                return null;
            }

        }.execute();

    }
    private Rule.HystrixConfig getHystrixConfig(GlobalContext ctx) {
        Rule rule = ctx.getRule();
        return rule.getHystrixConfig();
    }

    private void route(GlobalContext ctx) {
        Request request = ctx.getRequest().build();
        //通过nettyClient客服端向下游服务发送请求
        CompletableFuture<Response> future = AsyncHttpHelper.getInstance().executeRequest(request);
        //回调处理下游服务返回的结果
        future.whenCompleteAsync((response, throwable) -> {
            complete(request,response,throwable,ctx);
        });

    }
    private void complete(Request req, Response res,Throwable throwable,GlobalContext ctx) {
        //获取请求重试次数
        int currentRetryTimes = ctx.getCurrentRetryTimes();
        int confRetryTimes = ctx.getRule().getRetryConfig().getMaxAttempts();
        if((throwable instanceof TimeoutException || throwable instanceof IOException)
                &&currentRetryTimes<confRetryTimes){
            //重试请求
            retry(ctx);
            return;
        }

        try {
            if(throwable != null){
                String url = req.getUrl();
                if(throwable instanceof TimeoutException){
                    log.warn("request timeout:{}",url);
                    ctx.setReponse(GlobalResponse.buildGlobalResponse(ResponseCode.REQUEST_TIMEOUT));

                }else{
                    ctx.setReponse(GlobalResponse.buildGlobalResponse(ResponseCode.HTTP_RESPONSE_ERROR));

                }
            }
            else {
                ctx.setReponse(GlobalResponse.buildGlobalResponse(res));

            }
        } catch (Throwable t) {
            ctx.setReponse(GlobalResponse.buildGlobalResponse(ResponseCode.INTERNAL_ERROR));

            log.error("complete error", t);
        }finally {
            //释放请求
             ctx.releaseRequest();
             ctx.written();
             ResponseHelper.writeResponse(ctx);
        }
    }

    private void retry(GlobalContext ctx) {
        int currentRetryTimes = ctx.getCurrentRetryTimes();
        ctx.setCurrentRetryTimes(currentRetryTimes + 1);
        try {
            doFilter(ctx);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
